#include <json/json.h>
#include <fstream>
#include <thread>
#include "ConnectionPool.hpp"
using namespace Json;
using namespace std;

ConnectionPool* ConnectionPool::getConnectionPool() {
    // 静态的局部变量是线程安全的
    static ConnectionPool pool;    // 注意生命周期 访问范围受限制
    return &pool;
}

// 任务函数
void ConnectionPool::producerConnection() {
    // 生产数据库连接
    while (true) {
        unique_lock<mutex> lock(m_mutexQ);
        while (m_connectionQ.size() >= m_minSize) {
            // 小于最小连接数 就要生产  生产连接的线程就需要阻塞了
            m_cond.wait(lock);
        }
        // 需要创建新的数据库连接
        MysqlConn* conn = new MysqlConn;
        if (conn->connect(m_user, m_passwd, m_dbName, m_ip, m_port)) {
            conn->refreshAliveTime();
            m_connectionQ.push(conn);
            m_cond.notify_all();
        } else {
            delete conn;
        }
    }
}

void ConnectionPool::consumerConnection() {
    // 消费数据库连接
    // 空闲太多就销毁
    while (true) {
        // 每隔一秒钟就检测
        this_thread::sleep_for(chrono::milliseconds(500));

        // 加锁，访问完之后解锁
        lock_guard<mutex> lock(m_mutexQ);
        // 队列中的连接都是没有进行业务操作的
        while (m_connectionQ.size() > m_minSize) {
            // 队头的空闲时长肯定最长
            MysqlConn* conn = m_connectionQ.front();
            if (conn->getAliveTime() >= m_maxIdleTime) {
                m_connectionQ.pop();
                delete conn;
            } else {
                break;
            }
        }
    }
}

void ConnectionPool::addConnection() {
    MysqlConn* conn = new MysqlConn;
    if (conn->connect(m_user, m_passwd, m_dbName, m_ip, m_port)) {
        // 连接成功就记录时间戳
        conn->refreshAliveTime();
        m_connectionQ.push(conn);
    } else {
        delete conn;
    }
}

// 连接必须先存在
// 使用RALL原则和智能指针 就不需要手动去还连接
shared_ptr<MysqlConn> ConnectionPool::getConnection() {
    unique_lock<mutex> lock(m_mutexQ);
    // 先阻塞，阻塞等待 等到有连接
    while (m_connectionQ.empty()) {
        // 两种情况 
        // 1 指定的阻塞时长还没有达到 解除阻塞 唤醒函数
        // 2 指定时间段到达 没有被唤醒 进入timeout状态
        if (std::cv_status::timeout == m_cond.wait_for(lock, chrono::milliseconds(m_timeout))) {
            // 任务队列为null
            // 双重检测
            if (m_connectionQ.empty()) {
                continue;
            }
        }
    }
    // 指定删除器 第二个参数使用lambda
    shared_ptr<MysqlConn> connptr(m_connectionQ.front(), [this](MysqlConn* conn) {
        /*
        m_mutexQ.lock();
        // 然后 注意在放进队列之前 更新时间点
        conn->refreshAliveTime();
        // 不是要销毁conn指向的地址  而是回收该地址（将其放进数据库连接池的对应队列中）
        m_connectionQ.push(conn);
        // 关闭队列的连接状态
        m_mutexQ.unlock();
        */

        // 第二种使用lock_guard 可以自动对互斥锁进行加锁操作 对象被析构 首先解锁
        // 缺点 不能控制它锁定的数据块的范围 锁定的数据块就是lock对象创建的位置 -> 析构
        // 可以使用 {} 来控制范围
        lock_guard<mutex> lock(m_mutexQ);
        // 然后 注意在放进队列之前 更新时间点
        conn->refreshAliveTime();
        // 不是要销毁conn指向的地址  而是回收该地址（将其放进数据库连接池的对应队列中）
        m_connectionQ.push(conn);
    });
    m_connectionQ.pop();
    // 调用唤醒
    // 阻塞 唤醒生产者和消费者（附带） 
    m_cond.notify_all();

    return connptr;
}

// 加载配置文件
bool ConnectionPool::parseJsonFile() {
    ifstream ifs("dbconfig.json");
    Reader rd;
    Value root;
    rd.parse(ifs, root);
    if (root.isObject()) {
        m_ip = root["ip"].asString();
        m_port = root["port"].asInt();
        m_user = root["userName"].asString();
        m_passwd = root["passWord"].asString();
        m_dbName = root["dbName"].asString();
        m_minSize = root["minSize"].asInt();
        m_maxSize = root["maxSize"].asInt();
        m_maxIdleTime = root["maxIdleTime"].asInt();
        m_timeout = root["timeout"].asInt();
        return true;
    }
    return false;
}

// 构造
ConnectionPool::ConnectionPool() {
    // 加载配置文件
    if (!parseJsonFile()) {
        return;
    }
    for (int i = 0; i < m_minSize; i++) {
        addConnection();
    }
    // 空闲太多就要销毁

    // 多个线程 生产者-消费者模型
    thread producer(&ConnectionPool::producerConnection, this);
    thread consumer(&ConnectionPool::consumerConnection, this);

    // 主线程要做其他事情 不能阻塞 因此要分离
    producer.detach();
    consumer.detach();
}

// 析构
ConnectionPool::~ConnectionPool() {
    while (!m_connectionQ.empty()) {
        // 取出元素 依次释放
        MysqlConn* conn = m_connectionQ.front();
        m_connectionQ.pop();
        delete conn;
    }
}